Repository navigation
Web: add an opt-in storageBucket option for OPFS databases - #1142
Conversation
Keeps the database files of the OPFS-based VFS implementations in a named Storage Bucket instead of the default bucket of the origin private file system. The option is checked against the VFS when the options are resolved, and passed to the VFS through the worker's open options. Without the option nothing changes. Needs a @journeyapps/wa-sqlite release with the storageBucket option.
🦋 Changeset detectedLatest commit: 27c3d01 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
The wa-sqlite VFSs no longer take a bucket name. The database worker opens the bucket each time the VFS asks for its root directory, and prefixes the VFS lock and channel names with the bucket name. A browser without the Storage Buckets API gets a clear error, so apps set the option behind a feature check. The option's documentation covers bucket persistence and the SDK's cross-tab names, which stay keyed by the database name.
A bucket name the Storage Buckets API does not accept, and a browser without the API, are configuration errors at construction, so a wrong setting fails at once instead of in the worker after its retries. The option's documentation says how a named bucket gets persistent storage: the origin's navigator.storage.persist() first, then persist() on the bucket. The same-name test now uses the same name for every VFS, and the bucket tests are skipped where the API is missing.
|
Updated to match the reworked powersync-ja/wa-sqlite#95, where the VFSs take The option's documentation now also covers bucket persistence (the origin's Tests: |
main added additionalInitializationFunctions next to storageBucket in RawSqliteConnection.ts and WASQLiteOpenFactory.ts; both are kept.
rhashimoto/wa-sqlite#380 was merged with the option named prefix instead of lockPrefix.
A reader connection of OPFSWriteAheadVFS can briefly miss a write that just finished, with or without a bucket. Reading straight after a write made the tests fail now and then when the whole suite ran.
|
We have relesased you wa-sqlite changes in version |
2.0.9 has the getRoot and prefix VFS options that the storageBucket option needs.
|
Thanks @simolus3 for releasing it. I've moved the catalog entry to |
simolus3
left a comment
There was a problem hiding this comment.
Thanks! The approach here looks good to me.
- Use @journeyapps/wa-sqlite 2.0.10, since 2.0.9 had a build regression. - Shorten the option's documentation: configure the bucket by opening it before the database, and use unique database names across buckets. - Leave bucket name checks to the browser, and drop the second check for the Storage Buckets API, which vfsOptionsForStorageBucket already has.
Uses the
getRootandprefixoptions of the VFS implementations, from rhashimoto/wa-sqlite#380, released in@journeyapps/wa-sqlite2.0.9. This PR moves the catalog entry to 2.0.10, since 2.0.9 had a build regression.Summary
storageBucketopen option to@powersync/web. When it is set, the database files of the OPFS-based VFS implementations live in that Storage Bucket instead of the default bucket of the origin private file system.The problem
When a site's data is deleted, Chrome replaces the site's default storage bucket. Ordinary ways of deleting it all do this: Chrome's Delete browsing data dialog with its default choices, Site settings → the site → Delete data, and a server response with
Clear-Site-Data: "storage", which some apps send on sign-out. The DevTools Clear site data button does not. File and directory handles obtained before the deletion keep referring to the old bucket, while new handles refer to the new one. For the default bucket both refer to the same folder on disk, and Chrome then keeps two indexes for that folder.InvalidStateError: An operation that depends on state cached in an interface object was made but the state had changed since it was read from disk.Each call takes about 3 seconds to fail. The database cannot be opened in any tab, reloads do not help, and it lasts until the browser restarts or 10 minutes pass without OPFS use by any site. Retries keep it going.A PowerSync tab with an OPFS database open holds such handles, and the VFS uses them when the database is closed, when the tab reloads and when it closes. So an app cannot avoid the defect from outside the SDK. The Chromium internals, and the measurement showing that named buckets are deleted with the site's data like the default bucket, are in the wa-sqlite PR.
Steps to reproduce
With a PowerSync app
WASQLiteVFS.OPFSWriteAheadVFSorOPFSCoopSyncVFSin tab A, and let it open its database.chrome://settings/content/siteDetails?site=<the app's origin, URL-encoded>, choose Delete data and confirm with Delete.Result: tab B's database files are deleted. If instead, after step 2, another page only requested the directory before tab A reloaded, every OPFS call of the site fails from then on, and the app cannot open its database in any tab. We see this error in production when our PowerSync app opens its database. The two cases below were measured with
OPFSWriteAheadVFSopened the way PowerSync opens it, in a dedicated worker, on Chrome 154.0.8037.98:storageBucketsetIn the browser console, without PowerSync
Use a fresh profile and a page on an origin with OPFS, for example
http://localhost:8080/. The site needs some stored data, or Chrome does not offer Delete data.Tab A:
Delete the site's data as in step 2 above.
Tab A, without reloading:
await window.old.getFileHandle('after-delete', { create: true });Tab B:
This fails with the
InvalidStateErrorafter about 3 seconds, and so does every later OPFS call of the site.The same happens when step 2 uses Chrome's Delete browsing data dialog with its default choices, or a fetch of a same-origin URL whose response carries
Clear-Site-Data: "storage". Both were measured on Chrome 154.0.8037.98, with the same results as Site settings.With
(await navigator.storageBuckets.open('app-db')).getDirectory()in place ofnavigator.storage.getDirectory(), the call in step 3 fails at once and step 4 works. The wa-sqlite PR has a second variant that shows the deleted files, and a table of the measured results.What this PR changes
packages/web/src/db/adapters/options.ts: the documentedstorageBucketoption. The documentation covers the feature check, that the name is passed to the browser unchanged, how to configure the bucket (open it withnavigator.storageBuckets.open()before opening the database), and that database names should be unique even across buckets.resolveAndValidateOptions.ts: setting the option with a VFS that does not keep its files in OPFS is a configuration error.WASQLiteOpenFactory.tsandRawSqliteConnection.ts: the option travels to the database worker with the other open options. It is a string, so it crosses the worker boundary; a root directory function could not.vfs.ts:vfsOptionsForStorageBucket()maps the bucket name to the VFS options.getRootopens the bucket each time the VFS asks for its root, so a bucket the browser deleted with the rest of the site's data is created again.prefixis<bucket>:; bucket names cannot contain:, so two buckets never overlap. In a browser withoutnavigator.storageBucketsit throws an error that names the option and the missing API. The three OPFS VFS implementations receive these options.pnpm-workspace.yamland the lockfile:@journeyapps/wa-sqlite2.0.10.@powersync/webminor.Tests
packages/web/tests/storage_bucket.test.ts:vfsOptionsForStorageBucket(): no options without a bucket name; the bucket is opened on eachgetRoot()call andprefixis the bucket name with:; a missing Storage Buckets API throws.Run locally with
@journeyapps/wa-sqlite2.0.10 from npm, in Playwright's headless Chromium, as in CI:vitest run tests/storage_bucket.test.ts: 10 of 10 pass.@powersync/websuite: 25 files, 179 of 179 pass, in two runs. In earlier runs, failures in other files this PR does not touch (main.test.ts,multiple_tabs_iframe.test.ts,triggers.test.ts,multiple_instances.test.ts) came and went between runs.OPFSWriteAheadVFScan briefly miss a write that just finished, with or without a bucket: in four full-suite runs, a read straight after a write came back empty 6 times in the default storage and once in a bucket.Compatibility
navigator.storageBuckets.open()with those options before opening the PowerSync database.navigator.storageBucketsbefore setting it. The API is in Chromium-based browsers since version 122, and not in Firefox or Safari (caniuse).